iT邦幫忙

2026 iThome 鐵人賽

DAY 3
0
AI Engineering

Learning SRE for the AI Era:從 SRE Lab 到 Production AI Reliability系列 第 39 篇

Day 24(上)|Dashboard Design:首頁不是監控倉庫

  • 分享至 

  • xImage
  •  

GitHub:darkstar1227/learning-sre-for-ai-era

結論先說:好的 dashboard 由使用者影響往下鑽,第一頁先回答「現在要不要處理」,不是展示所有你量得到的數字。

換句話說,好 dashboard 的設計是減法——先決定 on-call 在午夜三點只剩十秒清醒頭腦時該看什麼,再用層級和連結把其他資訊藏到下面。

① The Question:dashboard 越建越多,為什麼故障時還是找不到該看哪張?

Day 22 把 Golden Signals、RED、USE 分好層了,Day 23 也把 trace 補完整了。訊號都有了,但沒人規定「打開 Grafana 後第一眼該看哪張」,dashboard 數量只會一直長。累積的路徑通常很固定:新服務上線帶來一份沒人客製過的預設 dashboard,每次 incident 又多留幾張「之後可能有用」的圖,新人找不到既有入口就自己再畫一張。沒人負責刪,最後 on-call 只能搜尋「latency dashboard」,而不是打開固定入口。Day 24 要處理的不是工具不足,而是缺少「進來先看哪一層、看完往哪裡鑽」的固定順序。

這個累積速度比想像中快,而且有跡可循。DEV Community 上一篇平台工程觀察〈Delete 40% of your dashboards〉整理了一次跨團隊的 dashboard 使用率稽核,結果是某個平台團隊累積了 340 張 dashboard,過去 30 天內卻只有 41 張被至少打開一次——換句話說有 299 張處於「殭屍狀態」,卻仍持續消耗查詢配額、佔用搜尋結果版面。文章觀察到多個團隊的通用比例落在 4:1 到 10:1 之間,也就是每被打開一張,背後就躺著三到九張沒人看的圖。這個數字之所以值得放進 Day 24 開頭,是因為它精準對應到本篇要處理的兩種浪費:一種是「量的浪費」——沒人刪、沒人審查,dashboard 數量單調遞增;另一種是「質的浪費」——就算打開了正確的那張,first screen 塞滿 panel,也找不到「現在要不要處理」的答案。本篇六層架構要同時處理這兩種浪費:層級順序處理「該看哪張」,contract 與驗收清單處理「這張圖夠不夠格留在首頁」。

累積這麼多 dashboard,通常不是因為團隊缺監控能力,而是把「建 dashboard」當成一次性工作,沒有把它當成需要持續維護的資產。這跟程式碼的技術債很像:沒有 code review 的程式碼會腐化,沒有 dashboard review 的監控畫面也會失去作用;只是後者不會噴 exception,而是在事故現場默默把人帶到錯的圖。

② Traditional SRE:從症狀往下鑽,不是把成因全部攤開

Google SRE Book 給過一個很明確的原則:先監控「症狀」,也就是使用者實際感受到的東西,不要一開始就把「成因」——CPU、cache 命中率、connection 數這類系統內部指標——全部攤在第一頁。理由很直接:成因一直在換,這次是 cache miss,下次可能是 connection pool 打滿,但「使用者有沒有正常拿到回應」這件事不會因為底層原因換了就跟著換。如果首頁直接塞滿成因層級的指標,等於要求 on-call 在事故現場,用人腦即時把一堆數字重新組合成「使用者是否受影響」,這件事應該在畫 dashboard 的時候就先做完。

依這個原則往下鑽,會排出一條固定順序:

User Experience → Service Overview → API → Dependencies → Infrastructure

首頁只放使用者能感受到的東西:SLO、error budget burn、主要 latency。第二層才是服務整體健康度——Day 22 的 Golden Signals。第三層拆到單一 API 或 endpoint,用 RED 看 rate、errors、duration。第四層是這個 API 依賴了誰。第五層才是 CPU、記憶體、磁碟這類底層資源,用 USE 看 utilization、saturation、errors。每往下鑽一層,讀者就換一次——第一層給值班工程師判斷「要不要處理」,最後一層給要修的人判斷「哪個資源撐不住」。

症狀(symptom)與成因(cause)不是同一種資訊,混在一起會互相稀釋

這個原則聽起來像常識,但容易被誤解成「症狀比較重要,所以只放症狀」。真正的重點不是重要性排序,是兩種資訊服務不同的決策,硬放在同一張圖上,反而會讓兩種決策都變差。

症狀(Symptom)                      成因(Cause)
———————————————                     ———————————————
使用者有沒有正常拿到回應?            CPU 用了多少?
error budget 燒了多快?               cache 命中率多少?
使用者等了多久?                      connection pool 剩多少?

回答「要不要處理」                    回答「怎麼修」
變動慢、訊號穩定                      變動快、雜訊多
on-call 決策用                        負責修的人決策用

症狀層的特徵是「訊號穩定」:不管這次是 cache miss、connection pool 打滿、還是 GPU 排隊,使用者感受到的東西只有「變慢了」或「失敗了」這幾種有限狀態,訊號本身不太會因為底層原因換了就跟著劇烈跳動。成因層剛好相反,它的特徵是「維度爆炸」:CPU、記憶體、cache、連線數、磁碟 IOPS,每個資源都有自己的正常波動範圍,而且彼此之間的正常關係會隨著流量模式改變。如果把這兩種特徵不同的資料畫在同一張首頁,穩定的症狀訊號會被吵雜的成因訊號蓋過去——on-call 打開畫面,看到十幾條線各自小幅波動,反而抓不到那條真正代表「使用者受影響」的線。

一個常見的誤解,是把這個原則讀成「dashboard 不該有成因層指標」。不是這樣。成因層一樣要監控,Day 24 的六層架構第三到第六層全部都是成因層,一個都沒有少。原則要限制的只有「首頁」——事故發生的頭十秒鐘,值班工程師打開瀏覽器分頁時看到的那張畫面。成因層該存在,只是不該在第一層,它該在使用者確認「有事要處理」之後,被當成第二步、第三步的鑽取目標,而不是第一步就要面對的資訊量。這也解釋了為什麼六層要用「鑽取」而不是「並列」的方式呈現:並列意味著十二張圖同時攤在眼前要求你判斷優先順序,鑽取意味著每次只需要看一張圖、做一個判斷,再決定要不要往下走一步。

③ AI Era Extension:多一層 GPU,也多一層 workflow 內部的鑽點

傳統五層鑽到 Infrastructure 就停了,AI workflow 還要再往下加一層:

… → Infrastructure → AI / GPU

GPU utilization、GPU memory saturation、inference queue 長度,是 USE 方法可以直接延伸過去的資源指標,只是資源從 CPU、磁碟換成 GPU、VRAM、推論佇列。

常見誤解:GPU utilization 高不等於系統健康,也不等於系統過載

把 CPU 監控的直覺原封不動搬到 GPU 上,是這一層最容易踩的坑。CPU utilization 高通常代表「忙」,utilization 低代表「閒」,這個直覺在傳統 web service 上大致成立。GPU 不是這樣:

CPU 的直覺                          GPU 的現實
——————————                          ——————————
utilization 90%+   → 過載警訊        utilization 90%+  → 可能是正常的批次推論
utilization 低      → 資源浪費        utilization 低     → 可能是 batch 還沒湊齊,
                                                            也可能是 queue 已經空了

原因出在 GPU 推論常見的 batching 機制。為了把昂貴的 GPU 算力用滿,推論伺服器(vLLM、TensorRT-LLM、TGI 這類 serving 框架)通常會把短時間內到達的多個請求打包成一個 batch 一起送進模型,而不是一個請求一次推論。這代表:

GPU utilization 高
      ≠
系統健康、還有餘裕

GPU utilization 高
      =
目前這批 batch 正在跑,
不知道 queue 裡還堆了多少在等

也就是說,光看 GPU utilization 這一條線,分不出「系統剛好滿載但游刃有餘」和「系統已經過載、大量請求在排隊」這兩種完全不同的處境。真正能回答「還撐不撐得住」的,是 inference queue 長度與queue 等待時間這兩個指標,而不是 utilization 本身——這正是 USE 方法裡 saturation(有多少工作在排隊)比 utilization(資源正在被用的比例)更關鍵的地方,只是多數人監控 GPU 時本能地先接 utilization,因為 nvidia-smi 最容易看到的就是這個數字,saturation 反而要從 serving 框架自己的 metrics endpoint 才拿得到。

把這個誤解落到 dashboard 設計上,AI/GPU 層至少要同時放這兩條線,而且要放在同一張圖裡對照著看:

┌─────────────────────────────────────────┐
│  GPU utilization(%)                    │
│  ─────────────────────────────           │
│  inference queue depth(請求數)         │
└─────────────────────────────────────────┘

utilization 高、queue depth 也高:真的過載,該擴容或降級。
utilization 高、queue depth 低:滿載但健康,這是好事,不用擴容。
utilization 低、queue depth 高:不是 GPU 不夠,可能是 batching 邏輯卡住、模型載入失敗或 worker 掛掉——這種情況擴 GPU 反而是花錢又沒用的錯誤反應,這一點在後面的 ⑩ Page 3 會用同一組對照再說明一次。

但 AI Era 真正麻煩的不是多一層資源,是 API 那一層本身要再往下拆。Day 23 已經把 /ask 的 span 拆成 prompt.build、retrieval.search、model.chat、tool.execute、answer.validate,如果 dashboard 的 API 層只顯示 /ask 整體的 RED,看不出這次變慢是 retrieval 慢、model 慢,還是某個 tool 卡住;要把 RED 拆到每個子步驟,才看得出瓶頸在哪一段。這一層要接的下一步,不是聚合數字,是 Day 23 定義的 trace search——點進去看單一 request 的 span tree,而不是把 prompt 或 request_id 直接塞進 panel label 當作「細節」,那些屬於 trace 該記的東西,不該出現在 metrics panel 上。

拆成子步驟不是為了畫面好看,而是因為傳統五層鑽到 API 那一層時,背後往往只是一支函式呼叫一個 SQL 或一個下游 REST API,/ask 整體 P95 慢,通常就等於那支函式慢,鑽一層就夠。AI workflow 不是這樣:/ask 一次請求內部串了檢索、模型呼叫、工具呼叫、輸出驗證,四段延遲分佈完全不同量級——retrieval.search 通常是幾十毫秒的資料庫查詢,model.chat 可能是幾百毫秒到數秒的網路呼叫,tool.execute 視外部服務而定,answer.validate 通常是本機運算的幾毫秒。把這四段疊在同一條 /ask P95 曲線上,等於把不同尺度的東西加總成一個數字,變慢的原因可能是其中任何一段,也可能是好幾段同時變慢又互相抵銷平均值。只看整體 P95,值班工程師唯一能做的判斷是「/ask 變慢了」,卻無法決定下一步該去查 retrieval 的索引、model provider 的狀態,還是某個 tool 的逾時設定——這正是 Day 22 定義 RED 的初衷落空的地方:RED 本該讓人不用猜就知道往哪裡查,一旦把五段揉成一段,RED 就退化回「有異常」這種和沒監控差不多的訊號。

④ SRE Lab:今日 DIY——用文字設計一份六層 dashboard 草圖

建立 dashboard.md,依前面排出的六層各列最多三項,每項附一句「看到紅色先點哪裡」:

## User Experience
- good outcome SLI(28 天、短窗)→ 點到 incident runbook
- latency SLO burn → 點到 error budget policy

## Service Overview
- /ask 整體 good outcome ratio → 點到下一層 API RED

## API
- /ask 拆 prompt_build / retrieve / model / tool RED → 點到 trace search

## Dependencies
- LLM provider 可用率、DB 連線狀態 → 點到 provider status page

## Infrastructure
- connection pool USE → 點到 pool 設定與擴容 runbook

## AI / GPU
- GPU utilization、inference queue 長度 → 點到 GPU 排程 dashboard

挑一個假想故障 upstream_timeout,從首頁走到 dependency 層,確認每一跳都有明確的下一步,答不出「看到紅色先做什麼」的項目就不該放進去。不需要建 Grafana,也不用填真實數字。

為什麼今天的 DIY 是寫文件,不是拉 Grafana panel

這個決定不是偷懶,是刻意的順序安排。前面 ①③ 講的「首頁該回答哪個問題」「症狀跟成因為什麼要分層」,都是設計層級的判斷,跟有沒有連上真正的 Prometheus 無關。如果一開始就打開 Grafana 拉 panel,很容易掉進「先把畫面弄漂亮」的陷阱,花時間調色、排版,卻沒人真正檢查過「這張圖回答的問題對不對」。

這呼應 Day 22 定義 metrics contract 的順序:先決定 signal 的語意,再決定用什麼工具收集它。用純文字寫 dashboard.md,逼自己在被畫面誘惑之前,先把六層骨架和每項「看到紅色先做什麼」寫清楚;等骨架站得住,晚一點換成真的 Grafana JSON,也只是換一種載體,不需要重新想一次。

這個 DIY 具體驗證的,是本文 ①②③ 提出的三個論點能不能真的落地成一份可執行的文件,而不是只停留在段落敘述:

論點(文章段落)                        DIY 驗證方式
——————————————                          ——————————————
症狀優先、成因往下鑽(② )              六層順序寫出來,
                                        User Experience 是否
                                        真的只放使用者能感受到的東西

每個 panel 要有明確下一步(① ⑥)       每一項是否都附了
                                        「看到紅色先點哪裡」

AI workflow 要拆到 span 層級(③)      API 層的三個項目是否
                                        真的細到 prompt_build /
                                        retrieve / model / tool

跑一次會踩到的坑:想不出「下一步」的項目,才是設計真正在起作用的地方

如果你照著上面的表格自己動手寫一份,大機率會卡在某幾個項目寫不出「看到紅色先點哪裡」。這不是你設計能力不夠,這正是這個練習要抓出來的東西——多數團隊現有的 dashboard,有相當比例的 panel 答不出這個問題,只是因為沒人問過。常見的卡點有兩種:

常見的卡點有兩種,都不該用「先寫著,之後再補」帶過去。第一種是「不知道正常值是多少」,例如 GPU utilization 要到多高才算異常——不要硬填一個「感覺對」的數字(回頭看 ⑯ 反例二「紅色門檻來自感覺不舒服」),沒有 SLO 或 baseline 支撐的門檻,寫進文件只是把噪音提前搬進設計階段。第二種是「沒有明確 owner」,寫到 Dependencies 層的 LLM provider 可用率時特別容易發生——provider 是外部服務,團隊內部誰該第一個被通知,很多組織其實從沒討論過。寫不出來,就代表這個項目還沒準備好放進 dashboard.md。

驗收

  • 六層各自有明確讀者,沒有把成因層的指標直接放進第一層。
  • 每個項目都有問題、owner、drill-down,答不出下一步的項目已被刪除。
  • 敏感與高基數資料(prompt、request_id)不在 metrics panel,只在 trace 裡查。
  • 文件草圖尚未接上任何真實監控資料,也沒有驗證過任何一個 panel。

本文未建立 dashboard,也沒有執行任何 dashboard drill-down;六層草圖是規劃練習。

⑤ Evidence:把六層草圖對回「誰看、看什麼、下一步去哪」

把 SRE Lab 畫出的六層,對回讀者與下一步,會得到一張對照表:

層級 讀者 回答的問題 看到紅色先做什麼
User Experience on-call 值班 使用者現在有沒有受影響 看 error budget 燒多快,決定要不要暫停 rollout
Service Overview on-call、值班主管 整個 /ask 服務健康嗎 往下切到 API 層找是哪個子步驟拖慢
API 負責該服務的工程師 是 prompt_build、retrieve、model 還是 tool 慢 點進 trace search 查單一 request 的 span tree
Dependencies 值班工程師 依賴的 provider、DB 是否正常 查 provider status page,決定是否 failover
Infrastructure 平台/SRE 團隊 底層資源是否撐得住 查 connection pool、worker 擴容 runbook
AI / GPU 平台/MLOps 團隊 GPU、推論佇列是否過載 查 GPU 排程 dashboard,決定是否降級或擴容

這張表本身沒有接上任何監控後端,是 SRE Lab 產出的規劃文件,不是可回放的觀測資料——這點延續 Day 22 的做法,今天的任務是排順序、定 owner,不是量測。

值得停下來多看一眼的,是「讀者」這一欄的變化:從 on-call 值班一路換到平台/MLOps 團隊,六層總共換了四種不同角色。這代表一份「完整的」dashboard,本來就不是設計給單一個人一次看完的——on-call 通常只需要走到第二、第三層就能決定要不要升級 incident 或找誰支援,第四層以後的角色多半是在 on-call 已經升級之後才被拉進來,各自從自己熟悉的那一層開始往下鑽,而不是重新從 User Experience 讀一遍。這也是為什麼 dashboard 的 owner 要按層級分開指定,而不是整份文件掛一個共同 owner:ai-platform 團隊清楚 API 和 Dependencies 兩層在測什麼,但不見得比 platform-sre 更懂 connection pool 的正常波動範圍,硬把 owner 統一成一個團隊,等於把跨團隊的職責邊界問題藏進一份看起來很乾淨的表格裡。

⑥ What I Learned:owner 和 drill-down 不是裝飾,是故障當下唯一能省下的東西

Cloudflare 在 2025 年 9 月 12 日發生過一次 Dashboard 與 API 中斷,事後的 postmortem 說明了根本原因:Dashboard 前端一段 React useEffect 的依賴陣列放了一個每次 render 都會重建的物件,導致這個 effect 每次都重新執行,讓原本只該呼叫一次的 Tenant Service API,在單一次畫面渲染裡被重複呼叫了非常多次。這波呼叫量又剛好撞上 Tenant Service 自己在進行版本更新,兩者疊加把它壓垮;由於 Tenant Service 是 Cloudflare API 授權邏輯的關鍵一環,它一倒,連帶讓一大票原本無關的 API 與 Dashboard 本身都跟著回應 5xx。

這起事件對 dashboard 設計的啟發是:如果值班工程師當下只看得到「Tenant API 呼叫量暴增」這一條聚合線,很難第一時間判斷這是正常尖峰,還是某個前端元件在瘋狂重複呼叫同一支 API;要能把呼叫量拆到來源、呼叫模式這類維度,才能在事故現場快速鎖定問題出在哪一層,而不是所有指標攤在同一張聚合圖上,逼人用猜的。

Cloudflare 事後檢討時,特別點出一個更具體的監控間隙:他們當時很難快速區分這波暴增的流量,究竟是真正的新請求,還是同一批請求在反覆重試。這個細節值得放大看,因為它剛好戳中一個很容易被忽略的 dashboard 設計盲點——多數團隊畫 request rate 圖時,只畫「總量」這一條線,卻沒有把「新請求」和「重試」拆成兩條分開的線。這兩者疊在一起看起來像同一種流量暴增,但代表的意義完全不同:新請求暴增通常代表真的有更多人在用系統,該考慮的是擴容;重試暴增則通常代表下游已經開始出錯,用戶端或其他服務正在瘋狂重打,該考慮的反而是要不要先擋住重試、避免雪崩式的 retry storm 把原本還撐得住的服務也一起打垮。如果 dashboard 的 request rate panel 沒有拆出 is_retry 這個維度,值班工程師在事故現場等於是在盲猜這波流量該用哪一種完全相反的處置方式。這也回應了 ⑬ 對 drill-down link 的要求——一個好的 panel 不只要告訴你「量變多了」,還要能一鑽就看到「量變多的原因屬於哪一種」。

另一個學到的教訓是,dashboard 數量本身就是一種技術債,而且是會複利成長的技術債。前面 ① 提到的稽核數字——340 張 dashboard 裡只有 41 張在過去 30 天被打開過,也就是每 8 張裡有 7 張是殭屍——不是個案,而是一個具有普遍性的累積模式:新服務上線帶預設 dashboard、每次 incident 留下臨時排查用的圖、季度 review 產出給主管看的匯總圖、新人找不到既有入口就自己畫一張,四種來源疊加起來,數量成長曲線只會比服務數量成長得更快。每張沒有 owner 的圖,故障時沒人知道該找誰確認;每張沒有 link 或 query 的圖,只是壁紙,還在背後持續消耗 query 配額與儲存成本。與其等 dashboard 累積到第 340 張才回頭整理,不如在建立當下就逼自己回答「這張圖的 owner 是誰、看到異常後第一步要去哪」,答不出來就先不要放進首頁——⑱ 驗收清單和 ⑯ 反例六會把這個原則落成具體的檢查項目和定期清理規則。

⑦ Production Takeaway

如果這是要上線的 production system,我會做三件事:先把首頁明確拆成 User Experience 到 AI/GPU 六層,禁止把成因層的指標直接放進第一頁;再替每張圖標上 owner 與下一步連結,讓草圖變成可執行的 runbook,而不是好看的分類海報;最後定期審查沒有查詢紀錄的 dashboard,寧可主動刪掉或封存,也不要讓它們持續消耗配額、混淆新人。dashboard 是事故時的地圖,不是工程成果展示。

這三件事的順序也很重要,不能反過來做。如果先做「定期清理」而不先把六層架構立起來,清理只會刪掉比較少人用的圖,卻留下一堆症狀和成因混在一起的「熱門但難用」的首頁——用得多不代表用得對,只是代表大家沒有更好的選擇。同樣地,如果先替現有的一團亂 dashboard 補 owner 和連結,等於是在替一份設計錯誤的文件做美化裝訂,owner 填了、連結加了,首頁還是同時塞了 SLO 和 CPU,問題並沒有真正解決。六層架構要先站穩,contract 和清理才有意義去執行,不然投入的力氣都是在原地打轉。

還有一件事我會在正式導入前先跟團隊講清楚:六層架構不是一次到位的重構,是持續維護的規範。第一版 dashboard 上線之後,隨著服務演化——新加一個 workflow step、換一個 model provider、GPU 換成不同架構——contract 裡的 metric 名稱、query、drill-down 目的地都會過期。如果沒有人固定排期去重新走一次 ⑰ 的六個 Step,contract 會慢慢變成另一種形式的殭屍 dashboard,只是這次是「文件說的和 panel 實際查的東西對不上」,而不是「沒人打開」。把 dashboard review 排進既有的服務 review 或 SLO review 節奏裡,而不是等出事才回頭補,是讓這套設計真正撐得住時間的最後一塊。

⑧ 先寫 dashboard contract,才開始拉 panel

dashboard 不是 Grafana 裡的一張畫布。

它是一份給事故現場使用的契約。

一張要長期存在的 dashboard,至少要回答下面八個問題。

欄位 必填內容 例子
名稱 清楚描述範圍 AI API / Service Overview
讀者 誰會在何時使用 /ask 的 on-call
決策 看完後要做哪個判斷 是否升級 incident
時間窗 預設看的期間 最近 30 分鐘
資料源 每個 panel 來自哪裡 Prometheus、Tempo、Loki
正常範圍 為什麼是綠色 SLO 或已知 baseline
下一步 異常時往哪裡鑽 trace search 或 runbook
Owner 指標與文件誰維護 ai-platform

少了讀者,dashboard 往往會變成某個人看得懂的私人筆記。

少了決策,它只是一面會動的牆。

少了下一步,它會在故障最忙的時候把問題丟回給人腦。

少了 owner,半年後閾值過期、查詢失效或連結 404,也沒有人知道應該修誰。

這八個欄位裡,「決策」和「下一步」最常被跳過,因為它們感覺起來像是廢話——建 dashboard 的人自己心裡通常有答案,只是懶得寫下來。但這正是問題所在:答案留在建圖那個人的腦子裡,半年後這個人可能已經轉組、離職,或單純忘記當初為什麼把某個門檻設成那個數字。八個欄位裡沒有一個是裝飾用的,它們合起來要做的事,是把「一個人腦中對這張圖的理解」搬到「任何人打開這份文件都能重建同樣理解」的狀態——這其實就是把 dashboard 當成程式碼的意思:程式碼也需要好的變數命名和註解,才能讓下一個讀的人不用回頭問原作者。

把 contract 放在 dashboard description,或放進同一個 repo 的 Markdown 都可以。

重要的是它和 panel 一起被 review。

下面是一個可直接調整的描述模板。

# AI API / Service Overview

## Purpose
判斷使用者是否受到 /ask 品質、錯誤或延遲影響。

## Primary reader
on-call engineer。

## Use during
告警觸發後的前五分鐘。

## Default window
Last 30 minutes;對照前一小時。

## Decision
若 good outcome SLI 與 latency SLO 同時惡化,建立或升級 incident。

## Drill-down
API RED → dependency dashboard → trace search → logs。

## Owner
ai-platform@example.invalid。

## Non-goals
不用於逐筆 prompt 檢閱,也不作為帳務對帳來源。

這段文字看起來不酷。

但它比多一張七彩 heatmap 更能降低交接成本。

一張圖只能有一個主要問題

panel 的標題最好是問題,不是 metric 名稱。

請比較下面兩種寫法。

request_duration_seconds_bucket
/ask 的 P95 latency 是否超過 SLO?

前者要求讀者先想起 metric 的資料型別,再推理它想說什麼。

後者直接告訴讀者判斷方向。

同樣的原則也適用於 legend。

好:primary model
好:fallback model
壞:route=primary
壞:route=fallback

label 不必完全消失。

但畫面上給人的文字要先服務人的決策,再服務資料模型的完整性。

把未知寫出來

圖表不應假裝知道不知道的事。

如果 quality evaluation 只覆蓋抽樣流量,panel title 就要說明它是抽樣。

如果某個 provider 沒有延遲直方圖,只能量到 client 端總耗時,也要在 description 說明。

Quality pass rate(10% sampled traffic;不是所有請求)

這不是削弱 dashboard。

這是在避免值班人員把局部資料誤當成全貌。

⑨ 首頁的四個判斷:影響、速度、範圍、變更

首頁不必很大。

它必須很快回答四個問題。

1. 使用者有沒有受影響?
2. 影響在擴大、持平,還是在恢復?
3. 影響集中在哪個 slice?
4. 最近是否有足以解釋變化的 deployment 或設定變更?

這四個問題各需要一小組 panel。

不是一個萬能 panel。

1. 使用者影響:先看 SLI,再看 component

對 Policy Q&A 服務來說,使用者影響可以是 good_outcome。

這個結果不能只等於 HTTP 200。

它至少要把 technical、workflow、quality 與 safety 的決策帶回來。

假設 metrics 已經把 outcome 收斂成低基數 label:

ai_requests_total{
  route="/ask",
  technical_status="completed",
  task_status="completed"
}

下面的 PromQL 是讀者可在自己的 Prometheus 環境調整的示意。

它沒有在本文環境執行。

sum(rate(ai_requests_total{
  route="/ask",
  technical_status="completed",
  task_status="completed"
}[5m]))
/
sum(rate(ai_requests_total{
  route="/ask"
}[5m]))

這個比率的名稱應該叫:

/ask good outcome ratio(5m)

不要把它命名成:

Availability

除非團隊已經在 SLO 文件定義過 availability 等於這個條件。

在 AI 系統裡,名字偷懶會造成定義漂移。

今天的 completed 可能只代表 parser 成功。

下個月你接入 evaluator 後,才開始代表品質通過。

兩者直接放在同一條歷史曲線,比較時會像把兩種尺混在一起。

這種定義漂移不是假設性的風險,它在 AI 系統裡特別容易發生,因為「品質評估」這件事本身就是逐步演化的:專案剛起步時,團隊往往只有能力判斷「有沒有回應」「有沒有丟例外」這種技術層面的成功與否;等評估基礎建設成熟了,才會慢慢加上語意品質、安全性檢查這些更貼近「使用者真的滿意嗎」的判斷。如果 good outcome 這個名稱從一開始就沒有把「目前只測到哪一層」寫進 dashboard description,這條曲線會在某個時間點悄悄變得更嚴格——不是系統突然變差了,是判斷標準本身變了——但畫面上看起來就像是一次沒有 deployment、沒有 annotation 可以解釋的品質下滑,逼著值班工程師在事故現場浪費時間排查一個根本不存在的故障。解法很直接:每次評估邏輯升級,good outcome 這個名稱底下涵蓋的判斷條件也要跟著在 dashboard description 或 metric contract 裡更新版本註記,讓歷史曲線的斷點有明確的解釋,而不是留給下一個看圖的人自己猜。

「首頁只看使用者感受,不看基礎設施」不是理論潔癖,OpenAI 在 2024 年 12 月的一次事故就是反面對照。當時是內部 DNS 快取系統失效,讓身份驗證服務找不到下游位址,多個服務的 CPU、記憶體、連線池這類基礎設施指標全程正常——沒有任何一項系統資源被打滿——但使用者端看到的是大量請求卡住不動,其中約 45% 的 API 請求最終逾時。如果值班工程師當下打開的是一張以 CPU、記憶體為主的基礎設施 dashboard,畫面會是一片綠色,得出「系統沒事」的錯誤結論;只有看使用者實際拿到回應的延遲與成功率,才會立刻看到問題。這正是本節前面強調的:good outcome ratio 或使用者端延遲要放在第一層,不是因為基礎設施指標不重要,而是因為基礎設施正常從來不保證使用者正常,這兩件事之間永遠可能存在一段沒被監控到的間隙(這次是 DNS 快取),只有直接量測使用者端的訊號,才不會被這段間隙騙過去。

2. 速度:看分位數,也看分解

平均 latency 不能代表使用者的等待感受。

一個很快的 batch request 足以把平均值拉低,卻救不了被慢請求卡住的使用者。

因此首頁的延遲圖可以先顯示 P50、P95、P99。

histogram_quantile(
  0.95,
  sum by (le) (
    rate(ai_request_duration_seconds_bucket{
      route="/ask"
    }[5m])
  )
)

如果你的 metric 名稱不同,請保留 histogram 的語意,而不要直接複製字串。

若 P95 變慢,首頁的工作到此為止:確認影響,點到下一層。

首頁不負責猜是 retrieval、model 或 tool。

那是 API workflow dashboard 的責任。

為什麼特別要挑 P50、P95、P99 這三個分位數,而不是隨便挑幾個數字:P50 代表典型使用者的體驗,用來確認「一般情況正不正常」;P99 代表系統目前能撐住的最壞情況,用來確認「有沒有一小撮人正在被拖垮」;P95 則是兩者之間的緩衝地帶,多數 SLO 也習慣用它當作正式承諾的門檻,因為它已經排除了 P99 那種偶發、極端的離群值,又比 P50 更能反映系統在有壓力時的表現。三條線一起看,而不是只挑一條,理由是它們各自可能獨立異常:P50 正常但 P99 飆高,代表少數請求(例如特別長的 context 或特別冷門的 retrieval 命中)被拖住,多數使用者感受不到;P50 和 P95 一起升高,則代表這不是少數離群值,而是系統性的變慢,值得立刻往下鑽。這正是 Day 22 定義 RED 時強調「Duration 要用分位數,不能用平均」的具體落地方式——平均值會把這兩種完全不同的處境,壓成同一個看起來普通的數字。

3. 範圍:切 slice,但不要切到不可讀

好的 slice 是能改變處置方式的維度。

例如:

environment
service_version
model_route
provider
workflow_version

不好的 slice 通常是每筆請求都不同的資料。

例如:

request_id
user_id
prompt
document_id
trace_id

後面這些不是不能收集。

它們應該去 trace 或 log,而不是 Prometheus label。

請記住一個容易被忽略的成本公式:

active time series
≈ metric combinations × label value combinations

一個 request_id 每次都不同,就等於讓 time series 數量跟流量一起成長。

在 incident 裡你得到的不是更多洞察,而是慢查詢和昂貴帳單。

套進具體數字更有感覺:假設 route(5 個值)、environment(3 個值)、model_route(2 個值)三個安全的低基數 label,加上 histogram 固定的 le bucket(12 個),組合出來的 time series 大約是 5 × 3 × 2 × 12 = 360 條,Prometheus 完全負擔得起。但只要多加一個 request_id(一天幾十萬個不同值),乘法立刻從 360 條炸成幾千萬條——公式本身是乘法結構,任何一個維度的基數暴增,都會直接乘上其他維度的組合數。多數團隊第一次踩到這個坑,不是不懂道理,而是開發環境流量太小、感覺不出差異,直到上線後流量放大,metrics backend 才在某次尖峰忽然變貴又變慢,而這時要把已經寫死在程式碼裡的 label 拿掉,往往比一開始設計對困難得多。

4. 變更:不要把相關性演成因果

deploy annotation 很有價值。

但它只回答「變更在何時發生」。

它不回答「這個變更造成了故障」。

把 deployment、prompt release、model route 切換、retrieval index 更新標成 annotation,可以縮短調查時間。

後續仍要查:

變更前後影響是否立即出現?
影響是否只在新版本流量出現?
rollback 後是否恢復?
是否同時有 provider 事件?

若沒有這些證據,就把它說成時間相關,不要說成根因。

⑩ 把 AI workflow dashboard 拆成可鑽取的三頁

六層架構聽起來像六張 dashboard。

實務上不必為每一層開一張獨立首頁。

對剛起步的 SRE Lab,三頁就夠。

Page 1:Service Overview
Page 2:Workflow and Dependencies
Page 3:Infrastructure and GPU

Page 1:Service Overview

目標是判斷是否有使用者影響。

固定放四組資訊。

SLI / SLO
Latency percentile
Request volume
Error-budget or burn-rate context

範例版面:

┌───────────────────┬───────────────────┐
│ Good outcome SLI  │ Error budget burn │
├───────────────────┼───────────────────┤
│ P95 /ask latency  │ Request rate      │
├───────────────────┴───────────────────┤
│ Deploy / prompt / model annotations    │
└───────────────────────────────────────┘

這頁不應出現十種 CPU。

CPU 是現在有沒有使用者影響的可能原因,不是答案本身。

Page 2:Workflow and Dependencies

目標是縮小 /ask 的失敗 domain。

可以依 Day 23 的 span tree 來安排 panel。

prompt.build
retrieval.search
model.chat
tool.execute
answer.validate

每一段建議至少能看到:

rate
error ratio
P95 latency
fallback or retry count(若適用)

範例查詢如下。

histogram_quantile(
  0.95,
  sum by (le, step) (
    rate(ai_workflow_step_duration_seconds_bucket{
      workflow_name="policy_qa"
    }[5m])
  )
)

這個 panel 的 legend 可以是:

prompt.build
retrieval.search
model.chat
tool.execute
answer.validate

不要把 step、route、environment 全塞進 legend。

應該用 dashboard variable 選定環境與 route,再讓 legend 專心比較步驟。

Grafana 的 dashboard model 支援 template variable,也支援 dashboard link 和 panel link。

它們不是為了讓畫面比較炫。

它們用來把目前選定的 service、environment、時間範圍,安全地帶到下一個調查畫面。

Page 3:Infrastructure and GPU

目標是確認資源是否限制 workflow。

它要回答的是:

排隊是因為沒有 worker?
GPU 是真的飽和,還是模型供應商慢?
記憶體壓力是否造成重啟?
connection pool 是否用盡?

把 resource 和 symptom 放同一時間軸,通常比把所有硬體指標排成九宮格更好。

例如把下面兩條線放在同一列:

inference queue depth
model.chat P95 latency

如果 queue 很高而 P95 同時升高,才值得再往 worker、GPU 或 batch policy 深挖。

如果 queue 很低但 provider latency 高,擴 GPU 反而可能是昂貴又錯誤的反應。

這正是 ③ 那段「GPU utilization 高不等於過載」的誤解,換一種畫面呈現方式再講一次。Page 3 存在的理由不是「把 GPU 相關的圖都塞進同一頁」,而是要讓平台或 MLOps 工程師一眼看出自己現在面對的是四種處置裡的哪一種:

queue 高 + P95 高    → 真的過載,擴容或降級
queue 低 + P95 高    → 不是 GPU 問題,查 provider 或網路
queue 高 + P95 低    → worker 還在消化,暫不用緊張
queue 低 + P95 低    → 一切正常

這張二維判斷表比任何單一指標都更接近 on-call 實際需要的答案,也是 Page 3 唯一值得放進首頁鑽取路徑的核心資訊——其餘 GPU 相關的細節指標,例如 VRAM 分配率、CUDA kernel 執行時間,屬於再往下一層的除錯資訊,不必也出現在 Page 3。

下集預告

六層架構和三頁版面決定了「該看哪張」,但每一張圖背後的 PromQL、Grafana variable、drill-down link 是否寫對,才決定這張圖在事故現場能不能被信任。下篇會把契約落實到查詢語法與連結設計,並用一次完整的故障鑽取演練與反例清單驗收整套設計。


這篇是 Learning SRE for the AI Era 系列的一部分。
Build → Trace → Break → Measure → Evaluate → Recover → Improve.


上一篇
Day 23(下)|LLM Observability:一條 Trace 要能還原工作流
下一篇
Day 24(下)|Dashboard Design:首頁不是監控倉庫
系列文
Learning SRE for the AI Era:從 SRE Lab 到 Production AI Reliability 共 44 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言